/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "nweb_delegate.h"

#include <thread>
#include "nweb_application.h"
#include "nweb_handler_delegate.h"
#include "nweb_render_handler.h"

#include "cef/include/base/cef_logging.h"
#include "cef/include/cef_app.h"
#include "nweb_preference_delegate.h"

namespace OHOS::NWeb {

static const float maxZoomFactor = 10.0;

class JavaScriptResultCallbackImpl : public CefJavaScriptResultCallback {
 public:
  JavaScriptResultCallbackImpl(
      std::shared_ptr<NWebValueCallback<std::string>> callback)
      : callback_(callback){};
  void OnJavaScriptExeResult(const CefString& result) override {
    if (callback_ != nullptr) {
      callback_->OnReceiveValue(result.ToString());
    }
  }

 private:
  std::shared_ptr<NWebValueCallback<std::string>> callback_;

  IMPLEMENT_REFCOUNTING(JavaScriptResultCallbackImpl);
  DISALLOW_COPY_AND_ASSIGN(JavaScriptResultCallbackImpl);
};

NWebDelegate::NWebDelegate(int argc, const char* argv[])
    : argc_(argc), argv_(argv) {}

NWebDelegate::~NWebDelegate() {
  if (display_listener_ != nullptr &&
      display_manager_adapter_ != nullptr) {
    display_manager_adapter_->UnregisterDisplayListener(display_listener_);
  }
}

bool NWebDelegate::Init(void* window) {
  preference_delegate_ = std::make_shared<NWebPreferenceDelegate>();

  display_manager_adapter_ =
    OhosAdapterHelper::GetInstance().CreateDisplayMgrAdapter();
  if (display_manager_adapter_ == nullptr) {
    return false;
  }

  display_listener_ =
    std::make_shared<DisplayScreenListener>(shared_from_this());
  if (display_listener_ == nullptr) {
    return false;
  }

  if (!display_manager_adapter_->RegisterDisplayListener(display_listener_)) {
    LOG(ERROR) << "jcz RegisterDisplayListener failed";
    return false;
  }

  render_handler_ = NWebRenderHandler::Create();
  if (render_handler_ == nullptr) {
    display_manager_adapter_->UnregisterDisplayListener(display_listener_);
    return false;
  }

  event_handler_ = NWebEventHandler::Create();
  if (event_handler_ == nullptr) {
    display_manager_adapter_->UnregisterDisplayListener(display_listener_);
    return false;
  }

  std::shared_ptr<DisplayAdapter> display =
    display_manager_adapter_->GetDefaultDisplay();
  if (display != nullptr) {
    NotifyScreenInfoChanged(display->GetRotation(), display->GetOrientation());
  }

  std::string url_for_init = "";
  InitializeCef(url_for_init, window);

  return true;
}

void NWebDelegate::OnDestroy(bool is_close_all) {
  if (display_listener_ != nullptr &&
      display_manager_adapter_ != nullptr) {
    display_manager_adapter_->UnregisterDisplayListener(display_listener_);
  }
  if (handler_delegate_ != nullptr) {
    handler_delegate_->OnDestroy();
  }
  if (preference_delegate_ != nullptr) {
    preference_delegate_->OnDestroy();
  }
}

void NWebDelegate::RegisterDownLoadListener(
    std::shared_ptr<NWebDownloadCallback> download_listener) {
  if (handler_delegate_ == nullptr) {
    LOG(ERROR) << "fail to register download listener, NWEB handler is nullptr";
    return;
  }
  handler_delegate_->RegisterDownLoadListener(download_listener);
}

void NWebDelegate::RegisterNWebHandler(std::shared_ptr<NWebHandler> handler) {
  if (handler_delegate_ == nullptr) {
    LOG(ERROR)
        << "fail to register nweb handler, nweb handler delegate is nullptr";
    return;
  }
  handler_delegate_->RegisterNWebHandler(handler);
}

void NWebDelegate::SetInputMethodClient(
    CefRefPtr<NWebInputMethodClient> client) {
  if (render_handler_ == nullptr) {
    LOG(ERROR)
        << "fail to register inputmethod client, render handler is nullptr";
    return;
  }
  render_handler_->SetInputMethodClient(client);
}

void NWebDelegate::RegisterRenderCb(
    std::function<void(const char*)> render_update_cb) {
  if (render_handler_ != nullptr) {
    render_handler_->RegisterRenderCb(render_update_cb);
  }
}

void NWebDelegate::Resize(uint32_t width, uint32_t height) {
  if (render_handler_ != nullptr) {
    render_handler_->Resize(width, height);
  }
  auto browser = GetBrowser();
  if (browser != nullptr && browser->GetHost() != nullptr) {
    browser->GetHost()->WasResized();
  }
}

void NWebDelegate::OnTouchPress(int32_t id, double x, double y) {
  if (event_handler_ != nullptr) {
    event_handler_->OnTouchPress(id, x, y);
  }
}

void NWebDelegate::OnTouchRelease(int32_t id, double x, double y) {
  if (event_handler_ != nullptr) {
    event_handler_->OnTouchRelease(id, x, y);
  }
}

void NWebDelegate::OnTouchMove(int32_t id, double x, double y) {
  if (event_handler_ != nullptr) {
    event_handler_->OnTouchMove(id, x, y);
  }
}

void NWebDelegate::OnTouchCancel() {
  if (event_handler_ != nullptr) {
    event_handler_->OnTouchCancel();
  }
}

bool NWebDelegate::SendKeyEvent(int32_t keyCode, int32_t keyAction) {
  bool retVal = false;
  if (event_handler_ != nullptr) {
    retVal = event_handler_->SendKeyEvent(keyCode, keyAction);
  }
  return retVal;
}

void NWebDelegate::SendMouseWheelEvent(double x, double y, double deltaX, double deltaY) {
  if (event_handler_ != nullptr) {
    event_handler_->SendMouseWheelEvent(x, y, deltaX, deltaY);
  }
}

void NWebDelegate::SendMouseEvent(int x, int y, int button, int action) {
  if (event_handler_ != nullptr) {
    event_handler_->SendMouseEvent(x, y, button, action);
  }
}

void NWebDelegate::NotifyScreenInfoChanged(
  RotationType rotation,
  OrientationType orientation) {
  if (render_handler_ != nullptr) {
    render_handler_->SetOrientation(rotation, orientation);
    auto browser = GetBrowser();
    if (browser != nullptr && browser->GetHost() != nullptr) {
      browser->GetHost()->NotifyScreenInfoChanged();
    }
  }
}

std::shared_ptr<NWebPreference> NWebDelegate::GetPreference() const {
  return preference_delegate_;
}

void NWebDelegate::Load(const std::string& url) {
  LOG(INFO) << "NWebDelegate::Load url=" << url;
  auto browser = GetBrowser();
  if (browser != nullptr) {
    browser->GetMainFrame()->LoadURL(CefString(url));
  }
  RequestVisitedHistory();
}

bool NWebDelegate::IsNavigatebackwardAllowed() const {
  LOG(INFO) << "NWebDelegate::IsNavigatebackwardAllowed";
  if (GetBrowser().get()) {
    return GetBrowser()->CanGoBack();
  }
  return false;
}

bool NWebDelegate::IsNavigateForwardAllowed() const {
  LOG(INFO) << "NWebDelegate::IsNavigateForwardAllowed";
  if (GetBrowser().get()) {
    return GetBrowser()->CanGoForward();
  }
  return false;
}

bool NWebDelegate::CanNavigateBackOrForward(int num_steps) const {
  LOG(INFO) << "NWebDelegate::CanNavigateBackOrForward";
  if (GetBrowser().get()) {
    return GetBrowser()->CanGoBackOrForward(num_steps);
  }
  return false;
}

void NWebDelegate::NavigateBack() const {
  LOG(INFO) << "NWebDelegate::NavigateBack";
  if (GetBrowser().get()) {
    GetBrowser()->GoBack();
  }
}

void NWebDelegate::NavigateForward() const {
  LOG(INFO) << "NWebDelegate::NavigateForward";
  if (GetBrowser().get()) {
    GetBrowser()->GoForward();
  }
}

void NWebDelegate::NavigateBackOrForward(int step) const {
  LOG(INFO) << "NWebDelegate::NavigateBackOrForward";
  if (GetBrowser().get()) {
    GetBrowser()->GoBackOrForward(step);
  }
}

void NWebDelegate::DeleteNavigateHistory() {
  LOG(INFO) << "NWebDelegate::DeleteNavigateHistory";
  if (GetBrowser().get()) {
    GetBrowser()->DeleteHistory();
  }
}

void NWebDelegate::Reload() const {
  LOG(INFO) << "NWebDelegate::Reload";
  if (GetBrowser().get()) {
    GetBrowser()->Reload();
  }
}

void NWebDelegate::ReloadOriginalUrl() const {
  LOG(INFO) << "NWebDelegate::ReloadOriginalUrl";
  if (GetBrowser().get()) {
    GetBrowser()->ReloadOriginalUrl();
  }
}

void NWebDelegate::Zoom(float zoomFactor) const {
  LOG(INFO) << "NWebDelegate::Zoom";
  if (GetBrowser().get()) {
    double curFactor = GetBrowser()->GetHost()->GetZoomLevel();
    GetBrowser()->GetHost()->SetZoomLevel(zoomFactor + curFactor);
  }
}

bool NWebDelegate::ZoomIn() const {
  LOG(INFO) << "NWebDelegate::ZoomIn";
  if (!GetBrowser().get()) {
    return false;
  }
  double curFactor = GetBrowser()->GetHost()->GetZoomLevel();
  if (zoom_in_factor_ + curFactor > maxZoomFactor) {
    return false;
  }
  GetBrowser()->GetHost()->SetZoomLevel(zoom_in_factor_ + curFactor);
  return true;
}

bool NWebDelegate::ZoomOut() const {
  LOG(INFO) << "NWebDelegate::ZoomOut";
  if (!GetBrowser().get()) {
    return false;
  }
  double curFactor = GetBrowser()->GetHost()->GetZoomLevel();
  if (zoom_in_factor_ + curFactor < 0) {
    return false;
  }
  GetBrowser()->GetHost()->SetZoomLevel(zoom_out_factor_ + curFactor);
  return true;
}

bool NWebDelegate::SetZoomInFactor(float factor)
{
  LOG(INFO) << "NWebDelegate::SetZoomInFactor";
  if (factor <= 0) {
    return false;
  }
  zoom_in_factor_ = factor;
  return true;
}

bool NWebDelegate::SetZoomOutFactor(float factor)
{
  LOG(INFO) << "NWebDelegate::SetZoomOutFactor";
  if (factor >= 0) {
    return false;
  }
  zoom_out_factor_ = factor;
  return true;
}

void NWebDelegate::Stop() const {
  LOG(INFO) << "NWebDelegate::Stop";
  if (GetBrowser().get()) {
    GetBrowser()->StopLoad();
  }
}

void NWebDelegate::ExecuteJavaScript(const std::string& code) const {
  LOG(INFO) << "NWebDelegate::ExecuteJavaScript";
  if (GetBrowser().get()) {
    GetBrowser()->GetMainFrame()->ExecuteJavaScript(
        code, GetBrowser()->GetMainFrame()->GetURL(), 0);
  }
}

void NWebDelegate::ExecuteJavaScript(
    const std::string& code,
    std::shared_ptr<NWebValueCallback<std::string>> callback) const {
  LOG(INFO) << "NWebDelegate::ExecuteJavaScript with callback";

  if (GetBrowser().get()) {
    CefRefPtr<JavaScriptResultCallbackImpl> JsResultCb =
        new JavaScriptResultCallbackImpl(callback);
    GetBrowser()->GetHost()->ExecuteJavaScript(code, JsResultCb);
  }
}

void NWebDelegate::PutBackgroundColor(int color) const {
  LOG(INFO) << "NWebDelegate::PutBackgroundColor";
  if (GetBrowser().get()) {
    GetBrowser()->GetHost()->SetBackgroundColor(color);
  }
}

void NWebDelegate::InitialScale(float scale) const {
  LOG(INFO) << "NWebDelegate::InitialScale";
  if (scale == intial_scale_) {
    return;
  }

  if (GetBrowser().get()) {
    GetBrowser()->GetHost()->SetInitialScale(scale);
  }
}

void NWebDelegate::OnPause() {
  LOG(INFO) << "NWebDelegate::OnPause";
  if (!GetBrowser().get()) {
    return;
  }

  // Remove focus from the browser.
  GetBrowser()->GetHost()->SendFocusEvent(false);

  if (!hidden_) {
    // Set the browser as hidden.
    GetBrowser()->GetHost()->WasHidden(true);
    hidden_ = true;
  }
}

void NWebDelegate::OnContinue() {
  LOG(INFO) << "NWebDelegate::OnContinue";
  if (!GetBrowser().get()) {
    return;
  }

  if (hidden_) {
    // Set the browser as visible.
    GetBrowser()->GetHost()->WasHidden(false);
    hidden_ = false;
  }

  // Give focus to the browser.
  GetBrowser()->GetHost()->SendFocusEvent(true);
}

void NWebDelegate::InitializeCef(std::string url, void* window) {
  handler_delegate_ = NWebHandlerDelegate::Create(
      preference_delegate_, render_handler_, event_handler_);
  nweb_app_ =
      new NWebApplication(preference_delegate_, url, handler_delegate_, window);

  CefMainArgs mainargs(argc_, const_cast<char**>(argv_));
  int exitcode = CefExecuteProcess(mainargs, nweb_app_, NULL);
  if (exitcode >= 0) {
    LOG(INFO) << "CefExecuteProcess returned : " << exitcode;
    return;
  }

  CefSettings settings;
  settings.windowless_rendering_enabled = true;
  settings.log_severity = LOGSEVERITY_INFO;
  settings.multi_threaded_message_loop = false;

  settings.persist_session_cookies = true;

#if !defined(CEF_USE_SANDBOX)
  settings.no_sandbox = true;
#endif

  static bool is_initialized = false;
  static std::mutex init_mtx;
  std::unique_lock<std::mutex> lk(init_mtx);
  if (is_initialized) {
    return nweb_app_->CreateBrowser();
  }

  if (!CefInitialize(mainargs, settings, nweb_app_, NULL)) {
    LOG(ERROR) << "CefInitialize failed";
  } else {
    is_initialized = true;
  }
}

void NWebDelegate::RunMessageLoop() {
  // Run the CEF message loop.
  // This will block until CefQuitMessageLoop() is called.
  CefRunMessageLoop();
}

std::string NWebDelegate::Title() {
  if (!GetBrowser().get()) {
    return "";
  }
  return GetBrowser()->GetHost()->Title();
}

HitTestResult NWebDelegate::GetHitTestResult() const {
  HitTestResult data;
  if (!GetBrowser().get()) {
    return data;
  }
  int type;
  CefString extra_data;
  GetBrowser()->GetHost()->GetHitData(type, extra_data);
  data.SetType(type);
  data.SetExtra(extra_data.ToString());
  return data;
}

int NWebDelegate::PageLoadProgress() {
  if (!GetBrowser().get()) {
    return 0;
  }
  return GetBrowser()->GetHost()->PageLoadProgress();
}

float NWebDelegate::Scale() {
  if (!GetBrowser().get()) {
    return 0;
  }
  return GetBrowser()->GetHost()->Scale();
}

void NWebDelegate::Load(
    std::string& url,
    std::map<std::string, std::string> additionalHttpHeaders) {
  std::map<std::string, std::string>::iterator iter;
  std::string extra = "";
  for (iter = additionalHttpHeaders.begin();
       iter != additionalHttpHeaders.end(); iter++) {
    const std::string& key = iter->first;
    const std::string& value = iter->second;
    if (!key.empty()) {
      // Delimit with "\r\n".
      if (!value.empty())
        extra += "\r\n";
      extra += std::string(key) + ": " + std::string(value);
    }
  }
  auto browser = GetBrowser();
  if (browser != nullptr) {
    browser->GetMainFrame()->LoadHeaderUrl(CefString(url), CefString(extra));
  }
  RequestVisitedHistory();
}

void NWebDelegate::LoadWithDataAndBaseUrl(const std::string& baseUrl,
                                          const std::string& data,
                                          const std::string& mimeType,
                                          const std::string& encoding,
                                          const std::string& historyUrl) {
  LOG(INFO) << "NWebDelegate::LoadWithDataAndBaseUrl";
  if (!GetBrowser().get()) {
    return;
  }
  GetBrowser()->GetHost()->LoadWithDataAndBaseUrl(baseUrl, data, mimeType,
                                                  encoding, historyUrl);
  RequestVisitedHistory();
}

void NWebDelegate::LoadWithData(const std::string& data,
                                const std::string& mimeType,
                                const std::string& encoding) {
  LOG(INFO) << "NWebDelegate::LoadWithData";
  if (!GetBrowser().get()) {
    return;
  }
  GetBrowser()->GetHost()->LoadWithData(data, mimeType, encoding);
  RequestVisitedHistory();
}

const CefRefPtr<CefBrowser> NWebDelegate::GetBrowser() const {
  if (handler_delegate_) {
    return handler_delegate_->GetBrowser();
  }
  return nullptr;
}

bool NWebDelegate::IsReady() {
  return GetBrowser() != nullptr;
}

void NWebDelegate::RequestVisitedHistory() {
  if (!GetBrowser().get()) {
    return;
  }
  if (!has_requested_visited_history) {
    has_requested_visited_history = true;
    if (handler_delegate_) {
      std::vector<std::string> outUrls = handler_delegate_->GetVisitedHistory();
      std::vector<CefString> urls = std::vector<CefString>();
      for (auto url : outUrls) {
        urls.push_back(url);
      }
      GetBrowser()->GetHost()->AddVisitedLinks(urls);
    }
  }
}

int NWebDelegate::ContentHeight() {
  if (render_handler_ != nullptr) {
    return render_handler_->ContentHeight();
  }
  return 0;
}

void NWebDelegate::RegisterArkJSfunction(
    const std::string& object_name,
    const std::vector<std::string>& method_list) const {
  LOG(INFO) << "RegisterArkJSfunction name : " << object_name.c_str();
  std::vector<CefString> method_vector;
  for (std::string method : method_list) {
    method_vector.push_back(method);
  }
  GetBrowser()->GetHost()->RegisterArkJSfunction(object_name, method_vector);
}

void NWebDelegate::UnregisterArkJSfunction(
    const std::string& object_name,
    const std::vector<std::string>& method_list) const {
  LOG(INFO) << "UnregisterArkJSfunction name : " << object_name.c_str();
  std::vector<CefString> method_vector;
  for (std::string method : method_list) {
    method_vector.push_back(method);
  }
  GetBrowser()->GetHost()->UnregisterArkJSfunction(object_name, method_vector);
}

void NWebDelegate::RegisterNWebJavaScriptCallBack(
    std::shared_ptr<NWebJavaScriptResultCallBack> callback) {
  if (handler_delegate_ == nullptr) {
    LOG(ERROR) << "fail to register NWEB client, NWEB handler is nullptr";
    return;
  }
  handler_delegate_->RegisterNWebJavaScriptCallBack(callback);
}

void NWebDelegate::OnFocus() const {
  LOG(INFO) << "NWebDelegate::OnFocus";
  if (!GetBrowser().get()) {
    return;
  }

  GetBrowser()->GetHost()->SendFocusEvent(true);
}

void NWebDelegate::OnBlur() const {
  LOG(INFO) << "NWebDelegate::OnBlur";
  if (!GetBrowser().get()) {
    return;
  }

  GetBrowser()->GetHost()->SendFocusEvent(false);
}
}  // namespace OHOS::NWeb
